package com.bcx.wind.workflow.db.datasource;

import com.bcx.wind.workflow.db.connection.WindConnection;
import com.bcx.wind.workflow.support.Assert;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;

/**
 * 工作流数据源
 *
 * @author zhanglei
 */
public class WindDataSource implements DataSource {

    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(WindDataSource.class);

    /**
     * 地址
     */
    private final String url;

    /**
     * driver驱动类
     */
    private final String driverClass;

    /**
     * 用户名称
     */
    private String userName;

    /**
     * 密码
     */
    private String password;

    /**
     * 连接池
     */
    private final ConnectionPool connPools = new ConnectionPool();

    /**
     * 最小连接池数量
     */
    private int minSize = 10;

    /**
     * 最大连接池数量
     */
    private int maxSize = 100;

    /**
     * 获取连接等待时间
     */
    private long getConnectionWaitTime = 20000L;

    /**
     * 连接使用超时时间
     */
    private long connectionOutTime = 20000L;

    /**
     * 获取连接尝试次数
     */
    private  int  tryTime = 3;

    /**
     * 是否自动提交
     */
    private boolean autoCommit = false;

    /**
     * 数据库隔离级别
     */
    private Integer transactionLevel;

    /**
     * 当前连接数量 包含正在使用的和正在闲置的
     */
    private int  activeConnectionCount;

    /**
     * 连接池是否已经初始化
     */
    private int init = 0;


    public static class ConnectionPool{

        /**
         * 闲置连接池
         */
        private final LinkedList<WindConnection>  connections = new LinkedList<>();

        /**
         * 正在使用的连接池
         */
        private final LinkedList<WindConnection>  activeConnections = new LinkedList<>();

        public LinkedList<WindConnection> getConnections() {
            return connections;
        }

        public LinkedList<WindConnection> getActiveConnections() {
            return activeConnections;
        }
    }


    public WindDataSource(String url,String driverClass){
        this.url = url;
        this.driverClass = driverClass;
    }

    public WindDataSource(String url,String driverClass,String userName,String password){
        this(url,driverClass);
        this.userName = userName;
        this.password = password;
    }

    public ConnectionPool getConnPools() {
        return connPools;
    }

    /**
     * 将使用完的连接放置到连接池中
     *
     * @param connection    闲置的连接
     * @throws SQLException  异常
     */
    public void pushConnection(WindConnection connection) throws SQLException{
        synchronized (this.connPools){

            //删除活跃连接池中的连接
            this.connPools.activeConnections.remove(connection);
            if(!connection.getConnection().getAutoCommit()){
                connection.getConnection().rollback();
            }

            //放到闲置连接池中
            if(!this.connPools.connections.contains(connection)) {
                this.connPools.connections.addLast(connection);
                this.connPools.notifyAll();
            }
        }
    }


    /**
     * 从连接池中获取连接
     *
     * @return    connection
     * @throws SQLException  异常
     */
    public WindConnection pullConnection() throws SQLException{
        synchronized (this.connPools) {
            //初始化连接池
            initPools();
            Connection connection = null;
            WindConnection conn = null;
            while(true) {
                //如果连接池存在闲置连接
                if (!this.connPools.connections.isEmpty()) {
                    conn = this.connPools.connections.removeFirst();
                    //添加到活跃连接池中
                    conn.setStartUseTime(System.currentTimeMillis());
                    this.connPools.activeConnections.addLast(conn);
                    break;
                }

                //不存在闲置连接，查看是否达到最大连接数量
                if (this.activeConnectionCount < this.maxSize) {
                    connection = doGetConnection();
                    this.activeConnectionCount++;
                    //添加到活跃连接池
                    conn = new WindConnection(connection, this);
                    conn.setStartUseTime(System.currentTimeMillis());
                    this.connPools.activeConnections.addLast(conn);
                    break;
                }

                //如果已经达到最大连接池数量，查看是否有超时的连接
                WindConnection activeFirstConn = this.connPools.activeConnections.getFirst();
                long time = activeFirstConn.getStartUseTime();
                if (System.currentTimeMillis() - time > this.connectionOutTime) {
                    //超时的连接，这里判断该连接为失效连接，直接关闭，重新新建连接
                    Connection connect = activeFirstConn.getConnection();
                    if (!connect.getAutoCommit()) {
                        try {
                            connect.rollback();
                        } catch (Exception e) {
                            e.printStackTrace();
                            if (LOGGER.isDebugEnabled()) {
                                LOGGER.debug("connection: " + connect + ",is can not rollback! because " + e.getMessage());
                            }
                        }
                    }
                    connect.close();
                    //从活跃线程池中删除连接
                    this.connPools.activeConnections.removeFirst();
                    connection = doGetConnection();
                    conn = new WindConnection(connection, this);
                    conn.setStartUseTime(System.currentTimeMillis());
                    this.connPools.activeConnections.addLast(conn);
                    break;
                }

                //如果还没有获取到则等待
                try {
                    this.connPools.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return conn;
        }
    }


    private Connection doGetConnection() throws SQLException {
        Connection connection = getConnection(this.userName,this.password);
        if(connection == null){
            //如果获取到的connection 为空，尝试重新获取
            int tryTime = this.tryTime;

            while(tryTime > 0){
                connection = getConnection(this.userName,this.password);
                if(connection != null){
                    break;
                }
                tryTime--;
            }
            Assert.notEmpty(tryTime+" time get connection fail! please confirm connection information is ok?",connection);
        }
        return connection;
    }


    /**
     * 初始化链接池
     */
    private void initPools(){
        if(this.init == 0){
            Assert.isTrue("sorry! dataSource maxSize must greater than minSize",minSize >= maxSize);
            for(int i=0 ; i<minSize ; i++){
                try {
                    Connection connection = doGetConnection();

                    //实例化工作流数据库连接
                    WindConnection conn = new WindConnection(connection,this);
                    this.connPools.connections.addLast(conn);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            this.activeConnectionCount = minSize;
            this.init = 1;
        }
    }

    @Override
    public Connection getConnection() throws SQLException {
        return pullConnection().getProxyConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        try {
            Class.forName(this.driverClass);
            Connection connection = DriverManager.getConnection(url,this.userName,this.password);
            connectionConfig(connection);

            if(LOGGER.isDebugEnabled()){
                LOGGER.debug("get connection success! "+connection);
            }
            return connection;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            if(LOGGER.isDebugEnabled()){
                LOGGER.debug("get connection fail! because "+e.getMessage());
            }
        }
        return null;
    }

    private void connectionConfig(Connection connection) throws SQLException {
        if(connection!=null){
            connection.setAutoCommit(this.autoCommit);

            if(this.transactionLevel != null){
                connection.setTransactionIsolation(this.transactionLevel);
            }
        }
    }

    public String getUrl() {
        return url;
    }

    public String getDriverClass() {
        return driverClass;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getMinSize() {
        return minSize;
    }

    public WindDataSource setMinSize(int minSize){
        this.minSize = minSize;
        return this;
    }

    public int getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    public long getGetConnectionWaitTime() {
        return getConnectionWaitTime;
    }

    public void setGetConnectionWaitTime(long getConnectionWaitTime) {
        this.getConnectionWaitTime = getConnectionWaitTime;
    }

    public int getTryTime() {
        return tryTime;
    }

    public void setTryTime(int tryTime) {
        this.tryTime = tryTime;
    }

    public boolean isAutoCommit() {
        return autoCommit;
    }

    public void setAutoCommit(boolean autoCommit) {
        this.autoCommit = autoCommit;
    }

    public Integer getTransactionLevel() {
        return transactionLevel;
    }

    public void setTransactionLevel(Integer transactionLevel) {
        this.transactionLevel = transactionLevel;
    }

    public long getConnectionOutTime() {
        return connectionOutTime;
    }

    public void setConnectionOutTime(long connectionOutTime) {
        this.connectionOutTime = connectionOutTime;
    }

    public int getActiveConnectionCount() {
        return activeConnectionCount;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        throw new SQLException(getClass().getName()+" is not a wrapper");
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return DriverManager.getLogWriter();
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        DriverManager.setLogWriter(out);
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        DriverManager.setLoginTimeout(seconds);
    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return DriverManager.getLoginTimeout();
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    }
}
